package com.lianqi.emcpframework.core.proxy;

import java.lang.annotation.Annotation;
import java.util.Arrays;
import java.util.Map;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Consumer;

import org.springframework.util.ClassUtils;

import javassist.*;
import javassist.bytecode.AnnotationsAttribute;
import javassist.bytecode.ConstPool;
import javassist.bytecode.annotation.*;
import lombok.Getter;
import lombok.SneakyThrows;

/**
 * @program emcp-framework
 * @ClassName Proxy
 * @description: 代理类定义
 * @author: sky
 * @create: 2018/11/15 20:40
 */
public class Proxy<I> {
    private static final AtomicLong counter = new AtomicLong(1);

    private CtClass ctClass;
    @Getter
    private Class<I> superClass;
    @Getter
    private String className;
    @Getter
    private String classFullName;

    private Class<I> targetClass;

    @SneakyThrows
    public static <I> Proxy<I> create(Class<I> superClass, String... classPathString) {
        return new Proxy<>(superClass, classPathString);
    }

    /**
     * 获取 target实例
     * 
     * @return
     */
    @SneakyThrows
    public I newInstance() {
        return getTargetClass().newInstance();
    }

    /**
     * 生成target类型 class
     * 
     * @return class target
     */
    @SneakyThrows
    private Class<I> getTargetClass() {
        if (targetClass == null) {
            targetClass = ctClass.toClass(ClassUtils.getDefaultClassLoader(), null);
        }
        return targetClass;
    }

    public Proxy<I> custom(Consumer<CtClass> ctClassConsumer) {
        ctClassConsumer.accept(ctClass);
        return this;
    }

    /**
     * 代理方法
     * 
     * @param superClass
     *            父类型
     * @param classPathString
     *            类路径
     */
    @SneakyThrows
    public Proxy(Class<I> superClass, String... classPathString) {
        if (superClass == null) {
            throw new NullPointerException("superClass can not be null");
        }
        this.superClass = superClass;
        ClassPool classPool = new ClassPool(true);

        ClassPath classPath = new ClassClassPath(this.getClass());
        classPool.insertClassPath(classPath);
        if (classPathString != null) {
            for (String path : classPathString) {
                classPool.insertClassPath(path);
            }
        }
        className = superClass.getSimpleName() + "_FastBeanCopier" + counter.getAndIncrement();
        classFullName = superClass.getPackage() + "." + className;
        ctClass = classPool.makeClass(classFullName);
        if (superClass != Object.class) {
            // 继承接口
            if (superClass.isInterface()) {
                ctClass.setInterfaces(new CtClass[] {classPool.get(superClass.getName())});
            } else {
                // 继承父类
                ctClass.setSuperclass(classPool.get(superClass.getName()));
            }
        }
        addConstructor("public " + className + "(){}");
    }

    /**
     * 添加构造方法
     * 
     * @param code
     *            构造方法定义
     * @return
     */
    public Proxy<I> addConstructor(String code) {
        return handleException(() -> ctClass.addConstructor(CtNewConstructor.make(code, ctClass)));
    }

    @SneakyThrows
    private Proxy<I> handleException(Task task) {
        task.run();
        return this;
    }

    interface Task {
        void run() throws Exception;
    }

    /**
     * 添加方法
     * 
     * @param code
     *            方法定义
     * @return
     */
    public Proxy<I> addMethod(String code) {
        return handleException(() -> ctClass.addMethod(CtMethod.make(code, ctClass)));
    }

    /**
     * 添加字段
     * 
     * @param code
     *            字段定义
     * @return
     */
    public Proxy<I> addField(String code) {
        return addField(code, null);
    }

    /**
     * 添加字段 使用code定义和注解
     * 
     * @param code
     *            字段定义
     * @param annotation
     *            注解 jdk自带注解
     * @return
     */
    public Proxy<I> addField(String code, Class<? extends java.lang.annotation.Annotation> annotation) {
        return addField(code, annotation, null);
    }

    /**
     * 添加字段 使用code定义和注解（包含注解属性）
     * 
     * @param code
     *            字段定义
     * @param annotation
     *            注解
     * @param annotationProperties
     *            注解属性定义
     * @return
     */
    public Proxy<I> addField(String code, Class<? extends Annotation> annotation,
        Map<String, Object> annotationProperties) {
        return handleException(() -> {
            CtField ctField = CtField.make(code, ctClass);
            if (null != annotation) {
                ConstPool constPool = ctClass.getClassFile().getConstPool();
                AnnotationsAttribute attribute = new AnnotationsAttribute(constPool, AnnotationsAttribute.visibleTag);
                javassist.bytecode.annotation.Annotation ann =
                    new javassist.bytecode.annotation.Annotation(annotation.getName(), constPool);
                if (null != annotationProperties) {
                    annotationProperties.forEach((key, value) -> {
                        MemberValue memberValue = createMemberValue(value, constPool);
                        if (memberValue != null) {
                            ann.addMemberValue(key, memberValue);
                        }
                    });
                }
                attribute.addAnnotation(ann);
                ctField.getFieldInfo().addAttribute(attribute);
            }
            ctClass.addField(ctField);
        });
    }

    /**
     * 注解属性生成
     * 
     * @param value
     *            属性值
     * @param constPool
     *            构造器
     * @return
     */
    private MemberValue createMemberValue(Object value, ConstPool constPool) {
        MemberValue memberValue = null;
        if (value instanceof Integer) {
            memberValue = new IntegerMemberValue(constPool, (Integer)value);
        } else if (value instanceof Long) {
            memberValue = new LongMemberValue((Long)value, constPool);
        } else if (value instanceof Boolean) {
            memberValue = new BooleanMemberValue((Boolean)value, constPool);
        } else if (value instanceof String) {
            memberValue = new StringMemberValue((String)value, constPool);
        } else if (value instanceof Class) {
            memberValue = new ClassMemberValue(((Class)value).getName(), constPool);
        } else if (value instanceof Object[]) {
            Object[] array = (Object[])value;
            ClassMemberValue classMemberValue = new ClassMemberValue(array[0].getClass().getName(), constPool);
            ArrayMemberValue arrayMemberValue = new ArrayMemberValue(classMemberValue, constPool);
            arrayMemberValue
                .setValue(Arrays.stream(array).map(o -> createMemberValue(o, constPool)).toArray(MemberValue[]::new));
            memberValue = arrayMemberValue;
        }
        return memberValue;
    }

}
